import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.math.BigInteger;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Calendar;
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.TimeoutException;


public class Helpers {
	
	public static int SERVERNAME = 0;
	public static int IPADDRESS = 1;
	public static int PORTNUMBER = 2;
	public static int PARENTMSD = 3;
	public static int HELLO_NUMCHILDREN = 3;
	public static int HELLO_IDHASH = 4;
	public static int ADDSPACE_RANGESTART = 3;
	public static int ADDSPACE_RANGEEND = 4;
	public static int REPHELLO_RANGESTART = 4;
	public static int REPHELLO_RANGEEND = 5;
	public static int CHECK_KEYWORD = 4;
	public static int REGISTER_KEYWORD = 4;
	public static int MSDPROBE_KEYWORD = 4;
	public static int MSDPROBE_HOPCOUNT = 7;
	public static int MSDPROBE_MSDIP = 5;
	public static int MSDPROBE_MSDPORT = 6;
	public static int SEARCH_PARAMETER = 4;
	public static int MSDPROBEREPLY_MSDIP = 5;
	public static int MSDPROBEREPLY_MSDPORT = 6;
	public static int MSDPROBEREPLY_HOPCOUNT = 7;
	public static int EXTSEARCH_PARAMETER = 4;
	public static int INVALIDATE_KEYWORD = 4;
	public static int QUERY_KEYWORD = 4;
	public static int REDIRECT_KEYWORD = 4;
	public static int REDIRECT_HOST = 5;
	public static int REDIRECT_PORT = 6;
	public static int SEARCHRESPONSE_KEYWORD = 5;
	public static int SEARCHRESPONSE_NAME = 6;
	public static int SEARCHRESPONSE_DETAILS = 6;
	public static int EXTSEARCHINVALID_KEYWORD = 4;
	public static int EXTSEARCHRESPONSE_DETAILS = 6;
	public static int EXTSEARCHRESPONSE_NAME = 6;
	public static int EXTSEARCHRESPONSE_KEYWORD = 5;
	public static int CHECKRESPONSE_TORF = 3;
	public static int REGISTERSTATUS_TORF = 3;
	public static int QUERYRESPONSE_NUMFIELDS = 2;
	public static int QUERYRESPONSE_DETAILS = 4;
	
	

	public static void waitForAllThreadsToClose(Thread[] threads, int timeoutSeconds) 
		throws TimeoutException
	{
		Calendar now = Calendar.getInstance();
		long start = now.getTimeInMillis();
		long timeout = start + timeoutSeconds*1000;
		
		boolean dead = false;
	    while(!dead)
	    {
	    	dead = true;
	    	for(Thread t : threads)
	    	{
	    		if(t != null)
	    			dead &= !t.isAlive();
	    	}
	    	now = Calendar.getInstance();
	    	if(now.getTimeInMillis() > timeout)
	    	{
	    		throw new TimeoutException(
	    				"Timed out waiting for all threads to close.");
	    	}
	    }		
	}

	public static boolean isParent(String[][] ServerConfigInfo, String[] server) {
		//parents are servers whose names (first col) appear in some other
		//servers parent column
		boolean isParent = false;
		for(String[] row : ServerConfigInfo)
		{
			if(row != null && row.length > Helpers.PARENTMSD)
				if(row[Helpers.PARENTMSD].equals(server[Helpers.SERVERNAME]))
				{
					isParent = true;
					break;
				}
		}
		return isParent;
	}

	public static boolean isChild(String[] server) {
		//children are servers whose parents are not "root"
		boolean isChild = true;
		if(server[Helpers.PARENTMSD].equals("root"))
			isChild = false;
		return isChild;
	}
	
	public static boolean isRoot(String[] server) {
		return !isChild(server);
	}

	public static int getNumAllChildren(String[][] serverConfigInfo, String[] server)
	{
		int count = 0;
		if(server == null)
			return 0;
		String[][] children = getChildren(serverConfigInfo, server);
		if(children == null)
			return 0;
		count = children.length; //for child encountered
		for(String[] child: children)
		{
			count += getNumAllChildren(serverConfigInfo, child);
		}
		return count;
	}
	
	public static String[][] getChildren(String[][] serverConfigInfo, String[] server)
	{
		String serverName = null;
		if(server != null && server.length > Helpers.SERVERNAME)
			serverName = server[Helpers.SERVERNAME];
		else
			return null;
		Queue<String[]> childrenQueue = new LinkedList<String[]>();
		for(String[] row : serverConfigInfo)
		{
			if(row != null && row.length > Helpers.PARENTMSD)
				if(row[Helpers.PARENTMSD].equals(serverName))
				{
					childrenQueue.add(row);
				}
		}
		if(childrenQueue.size() <= 0)
			return null;
		int childrenIndex = 0;
		String[][] children = new String[childrenQueue.size()][];
		while(childrenQueue.size() > 0)
		{
			children[childrenIndex] = childrenQueue.remove();
			childrenIndex++;
		}
		return children;
	}
	
	public static String[] getParent(String[][] serverConfigInfo, String[] server)
	{
		String parentServerName = null;
		if(server != null && server.length > Helpers.PARENTMSD)
			parentServerName = server[Helpers.PARENTMSD];
		else
			return null;
		
		for(String[] row : serverConfigInfo)
		{
			if(row != null && row.length > Helpers.SERVERNAME)
				if(row[Helpers.SERVERNAME].equals(parentServerName))
				{
					return row;
				}
		}
		return null;
	}
	
	public static String getMD5Hash(String pass) {
		MessageDigest m;
		try {
			m = MessageDigest.getInstance("MD5");
		} catch (NoSuchAlgorithmException e) {
			return null;
		}
		byte[] data = pass.getBytes(); 
		m.update(data,0,data.length);
		BigInteger i = new BigInteger(1,m.digest());
		return String.format("%1$032X", i);
	}

	public static String[][] getHashBoundaries(String addressStartHex32,
			String addressEndHex32, int numSpaces) 
	{
		BigInteger numSpacesBI = new BigInteger(Integer.toString(numSpaces));
		BigInteger addressStartBI = new BigInteger(addressStartHex32, 16);
		BigInteger addressEndBI = new BigInteger(addressEndHex32, 16);
		BigInteger width = addressEndBI.subtract(addressStartBI);
		BigInteger nodeWidth = width.divide(numSpacesBI);
		BigInteger remainder = width.mod(numSpacesBI);
		  
		String[][] keywordHashBoundaries = new String[numSpaces][2];
		BigInteger start = addressStartBI;
		BigInteger end;
		for(int i = 0; i < numSpaces; i++)
		{
			BigInteger iBI = new BigInteger(Integer.toString(i));
			end = start.add(nodeWidth);
			if(i == numSpaces - 1)
				end = addressEndBI;
			keywordHashBoundaries[i][0] = String.format("%1$032X", start);
			keywordHashBoundaries[i][1] = String.format("%1$032X", end);
			start = end.add(BigInteger.ONE);
		}
		
		return keywordHashBoundaries;
	}

	public static void sendMessage(String[] server, String message) 
		throws UnknownHostException, IOException{
		Socket socket = null;
        PrintWriter out = null;

        socket = new Socket(server[Helpers.IPADDRESS], Integer.parseInt(server[Helpers.PORTNUMBER]));
        out = new PrintWriter(socket.getOutputStream(), true);

        System.out.println("  Helpers.sendMessage: --> " + server[0] + ": " + message);
        System.out.flush();
        out.println(message);		
	
        out.println("bye 0x0B 0");
        
		out.close();
		socket.close();
	}
	
	public static String sendMessageRcvReply(String[] server, String message) 
		throws UnknownHostException, IOException{
		Socket socket = null;
	    PrintWriter out = null;
	    BufferedReader in = null;
	
	    socket = new Socket(server[Helpers.IPADDRESS], Integer.parseInt(server[Helpers.PORTNUMBER]));
	    out = new PrintWriter(socket.getOutputStream(), true);
	    in = new BufferedReader(new InputStreamReader(
	                                socket.getInputStream()));
	    
	    System.out.println("  Helpers.sendMessageRcvReply: --> " + server[0] + ": " + message);
	    System.out.flush();
	    out.println(message);
	    String reply = in.readLine();
	    System.out.println("  Helpers.sendMessageRcvReply: <-- " + server[0] + ": " + reply);
	    System.out.flush();
	    
	    out.println("bye 0x0B 0");
	    
		out.close();
		in.close();
		socket.close();
		return reply;
	}
	
	public static String[] extractFields(String line, String delimeter, int startIndex)
	{
		String[] fields = line.split(delimeter);
		Queue<String> nonEmptyFieldsQueue = new LinkedList<String>();
		
		for (int i=0; i < fields.length; i++)
			if(fields[i].trim().length() > 0)
				nonEmptyFieldsQueue.add(fields[i].trim()); //trim any whitespace from each field
		
		String[] nonEmptyFields = new String[nonEmptyFieldsQueue.size()];
		int index = 0;
		int nonEmptyFieldsIndex = 0;
		while(nonEmptyFieldsQueue.size() > 0)
		{
			String val = nonEmptyFieldsQueue.remove();
			if(index >= startIndex)
			{
				nonEmptyFields[nonEmptyFieldsIndex] = val;
				nonEmptyFieldsIndex++;
			}
			index++;
		}
		return nonEmptyFields;
	}
	
	public static String[] extractFields(String line, String delimeter)
	{
		return extractFields(line, delimeter, 0);
	}
	
	public static String arrayToDelimitedList(String[] input, String delimeter)
	{
		if(input==null)
			return null;
		String result = "";
		for(int i=0; i<input.length; i++)
		{
			if(input[i] == null || input[i].trim().length() == 0)
				continue;
			result += input[i];
			if(i<input.length-1)
				result += delimeter;
		}
		return result;
	}
}
